<template>
	<canvas id="timeline"></canvas>
</template>

<script setup lang="ts">
import { ref, onMounted, computed, toRefs, watchEffect } from "vue"
import { type CanvasTimeAxisProps } from "@/types/index"
import dayjs from "dayjs"
import { useDebounceFn } from "@vueuse/core"

const props = withDefaults(defineProps<CanvasTimeAxisProps>(), {
	height: 100,
	backgroundColor: "#222222",
	textColor: "#ffffff",
	axisLineColor: "#ffffff",
	maskColor: "rgba(101, 205, 0,.5)",
	isInfinite: true,
	startTime: "00:15:23",
	endTime: "00:29:14",
	timeGap: 1,
	pixelGap: 50,
	offset: 0,
	smallAxisLineHeight: 50,
	largeAxisLineHeight: 100,
	timeOffsetLeft: 20,
	timeOffsetTop: 60
})
// 画布
const canvas = ref<HTMLCanvasElement | null>(null)
// 上下文
const ctx = ref<CanvasRenderingContext2D | null>(null)
// 解构属性
const {
	startTime,
	endTime,
	timeGap,
	pixelGap,
	offset,
	backgroundColor,
	smallAxisLineHeight,
	largeAxisLineHeight,
	axisLineColor,
	textColor,
	timeOffsetLeft,
	timeOffsetTop,
	maskColor,
	isInfinite,
	height
} = toRefs(props)
// 第一个刻度时间
const firstTime = ref(0)
// 时间轴开始时间转秒
const axisStartAsSeconds = ref(0)
// 时间轴结束时间转秒
const axisEndAsSeconds = ref(0)
// 总时长
const duration = ref(0)
watchEffect(() => {
	const [startHour, startMinute, startSecond] = startTime.value.split(":")
	const [endHour, endMinute, endSecond] = endTime.value.split(":")
	const end = dayjs.duration({
		hours: Number(endHour),
		minutes: Number(endMinute),
		seconds: Number(endSecond)
	})
	const start = dayjs.duration({
		hours: Number(startHour),
		minutes: Number(startMinute),
		seconds: Number(startSecond)
	})
	if (!isInfinite.value) {
		const [startHour, startMinute, startSecond] = startTime.value.split(":")
		const start = dayjs.duration({
			hours: Number(startHour),
			minutes: Number(startMinute),
			seconds: Number(startSecond)
		})
		firstTime.value = start.asSeconds()
	} else {
		firstTime.value = 0
	}
	axisStartAsSeconds.value = start.asSeconds()
	axisEndAsSeconds.value = end.asSeconds()
	duration.value = end.asSeconds() - start.asSeconds()
})
// 需要绘制的总宽度
const needDrawWidth = computed(
	() =>
		Math.round(duration.value / timeGap.value) * pixelGap.value + offset.value
)
// 整个canvas能够画多长时间
const totalCanDrawSeconds = computed(() => {
	if (!canvas.value) return 0
	return Math.floor(canvas.value.width / pixelGap.value) * timeGap.value
})
// 当前已绘制的长度
const havedDrawWidth = ref(0)
// 已经绘制的时长
const havedDrawDuration = ref(0)
// 处理鼠标滚轮事件
const handleMousewheel = useDebounceFn((e: WheelEvent) => {
	if (!ctx.value || !canvas.value) return
	if (e.deltaY <= 0) {
		// console.log("此时还未改", havedDrawDuration.value)
		havedDrawDuration.value =
			havedDrawDuration.value - timeGap.value * 100 - totalCanDrawSeconds.value
		// 这里还要判断是否低于开始时间
		if (!isInfinite.value && havedDrawDuration.value < axisStartAsSeconds.value)
			havedDrawDuration.value = axisStartAsSeconds.value
		if (havedDrawDuration.value < 0) havedDrawDuration.value = 0
	}
	firstTime.value = havedDrawDuration.value
	// 不是无限刻度且已经超过要画的时长就不画了
	if (
		!isInfinite.value &&
		axisEndAsSeconds.value &&
		havedDrawDuration.value > axisEndAsSeconds.value
	)
		return
	requestAnimationFrame(draw)
}, 30)
// 初始化
function initCanvas() {
	canvas.value = document.getElementById("timeline") as HTMLCanvasElement
	ctx.value = canvas.value.getContext("2d")
	canvas.value.width = window.innerWidth * window.devicePixelRatio
	canvas.value.height = height.value * window.devicePixelRatio
	canvas.value.style.backgroundColor = backgroundColor.value
	window.addEventListener("wheel", handleMousewheel)
}
// 与第一个时间距离多少个间隔
function distanceGapWithFirst(curSecond: number) {
	return Math.round((curSecond - firstTime.value) / timeGap.value)
}
// 格式化时间
function formatTime(second: number) {
	const time = dayjs.duration(second, "seconds")
	return `${time.hours() > 9 ? time.hours() : "0" + time.hours()}:${
		time.minutes() > 9 ? time.minutes() : "0" + time.minutes()
	}:${time.seconds() > 9 ? time.seconds() : "0" + time.seconds()}`
}
// 画时间的时机
function shouldDrawTime(curSecond: number) {
	console.log("当前", curSecond)
	return (
		curSecond % (timeGap.value * 10) === 0 ||
		(!isInfinite.value && curSecond === axisStartAsSeconds.value) ||
		(!isInfinite.value && curSecond === axisEndAsSeconds.value)
	)
}
// 画线
function drawLine(curSecond: number) {
	if (!ctx.value) return
	ctx.value.beginPath()
	ctx.value.moveTo(
		distanceGapWithFirst(curSecond) * pixelGap.value + offset.value,
		0
	)
	ctx.value.lineTo(
		distanceGapWithFirst(curSecond) * pixelGap.value + offset.value,
		shouldDrawTime(curSecond)
			? largeAxisLineHeight.value
			: smallAxisLineHeight.value
	)
	ctx.value.strokeStyle = axisLineColor.value
	ctx.value.lineWidth = 1
	ctx.value.lineCap = "round"
	ctx.value.stroke()
}
// 画时间
function drawTimeText(curSecond: number) {
	if (!ctx.value) return
	ctx.value.font = "20px Arial"
	ctx.value.fillStyle = textColor.value
	ctx.value.fillText(
		formatTime(curSecond),
		distanceGapWithFirst(curSecond) * pixelGap.value +
			offset.value +
			timeOffsetLeft.value,
		smallAxisLineHeight.value + timeOffsetTop.value
	)
}
// 画小刻度
function drawSecondText(curSecond: number) {
	if (!ctx.value) return
	let text = `${curSecond % (timeGap.value * 10)}`
	ctx.value.font = "20px Arial"
	ctx.value.fillStyle = textColor.value
	ctx.value.fillText(
		text,
		distanceGapWithFirst(curSecond) * pixelGap.value + offset.value,
		smallAxisLineHeight.value + timeOffsetTop.value - 30
	)
}
// 画选中范围
function drawSelectedRanges() {
	if (!ctx.value || !canvas.value) return
	const selectedStart = 836
	const selectedEnd = 1450
	const range = selectedEnd - selectedStart
	// 超过时间轴范围不画
	if (
		!isInfinite.value &&
		(selectedStart >= axisEndAsSeconds.value ||
			selectedEnd <= axisStartAsSeconds.value)
	)
		return
	if (!isInfinite.value && duration.value < range) return
	let rangeStartX = 0
	let rangeEndX = 0
	// console.log("选择的范围", range, "canvas能画", totalCanDrawSeconds.value)
	// 1.当前第一个时长在选择的范围右边，大于范围不用画
	if (firstTime.value >= selectedEnd) return
	// 2.当前第一个时长在选择的范围左边，小于范围
	if (firstTime.value <= selectedStart) {
		rangeStartX =
			Math.floor((selectedStart - firstTime.value) / timeGap.value) *
				pixelGap.value +
			(((selectedStart - firstTime.value) % timeGap.value) / timeGap.value) *
				pixelGap.value
		// 不超过范围
		if (range <= totalCanDrawSeconds.value) {
			rangeEndX =
				Math.floor((selectedEnd - firstTime.value) / timeGap.value) *
					pixelGap.value +
				(((selectedEnd - firstTime.value) % timeGap.value) / timeGap.value) *
					pixelGap.value
		} else {
			// 超过了画选择的前半部分
			rangeEndX = canvas.value.width - rangeStartX
		}
	}
	// 3.当前第一个时长在选择的范围中间
	if (firstTime.value <= selectedEnd && firstTime.value >= selectedStart) {
		rangeStartX = 0
		rangeEndX =
			Math.floor((selectedEnd - firstTime.value) / timeGap.value) *
				pixelGap.value +
			(((selectedEnd - firstTime.value) % timeGap.value) / timeGap.value) *
				pixelGap.value
	}
	ctx.value.fillStyle = maskColor.value
	ctx.value.fillRect(rangeStartX, 0, rangeEndX, 50)
}
// 绘制
function draw() {
	if (!ctx.value || !canvas.value) return
	// 清空画布
	ctx.value.clearRect(0, 0, canvas.value.width, canvas.value.height)
	drawSelectedRanges()
	for (let i = 0; i <= canvas.value.width; i += pixelGap.value) {
		// 记录已经绘制了多少刻度的时间，加上第一个时间就是准确的
		havedDrawDuration.value =
			Math.round(i / pixelGap.value) * timeGap.value + firstTime.value
		// 记录已经绘制了多少宽度
		havedDrawWidth.value =
			Math.round(havedDrawDuration.value / timeGap.value) * pixelGap.value
		// 不是无限刻度且已经超过要画的时长就不画了
		if (
			!isInfinite.value &&
			axisEndAsSeconds.value &&
			havedDrawDuration.value > axisEndAsSeconds.value
		)
			break
		drawLine(havedDrawDuration.value)
		if (shouldDrawTime(havedDrawDuration.value)) {
			drawTimeText(havedDrawDuration.value)
		} else {
			drawSecondText(havedDrawDuration.value)
		}
	}
}
onMounted(() => {
	initCanvas()
	requestAnimationFrame(draw)
})
defineExpose({
	needDrawWidth,
	havedDrawDuration,
	havedDrawWidth,
	firstTime
})
</script>

<style scoped>
canvas {
	width: 100vw;
	height: 100px;
	background-color: var(--vt-c-black-soft);
}
</style>
